﻿//MarketDepthSerializer.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Algo.Storages
{
	using System;
	using System.Collections.Generic;
	using System.IO;
	using System.Linq;

	using Ecng.Common;
	using Ecng.Serialization;

	using StockSharp.BusinessEntities;

	class MarketDepthMetaInfo : MetaInfo<MarketDepthMetaInfo>
	{
		public MarketDepthMetaInfo(DateTime date)
			: base(date)
		{
			FirstPrice = -1;
		}

		public override void Write(Stream s)
		{
			base.Write(s);

			s.Write(FirstPrice);
			s.Write(LastPrice);
		}

		public override void Read(Stream s)
		{
			base.Read(s);

			FirstPrice = s.Read<decimal>();
			LastPrice = s.Read<decimal>();
		}

		protected override void CopyFrom(MarketDepthMetaInfo src)
		{
			base.CopyFrom(src);

			FirstPrice = src.FirstPrice;
			LastPrice = src.LastPrice;
		}
	}

	class MarketDepthSerializer : MarketDataSerializer<MarketDepth, MarketDepthMetaInfo>
	{
		public MarketDepthSerializer(Security security)
			: base(security, 16 + 20 * 25)
		{
		}

		protected override void OnSave(List<bool> bits, IEnumerable<MarketDepth> depths, MarketDepthMetaInfo metaInfo)
		{
			if (metaInfo.IsEmpty())
			{
				var firstDepth = depths.FirstOrDefault(d => d.BestPair.Bid != null || d.BestPair.Ask != null);

				if (firstDepth == null)
					throw new ArgumentException("Все переданные стаканы является пустыми.", "depths");

				metaInfo.LastPrice = metaInfo.FirstPrice = GetDepthPrice(firstDepth);
				
				//pyh: будет баг если первый стакан пустой и с другим временем.
				//metaInfo.FirstTime = firstDepth.LastChangeTime;
			}

			bits.SerializeInt(depths.Count());

			MarketDepth prevDepth = null;

			foreach (var depth in depths)
			{
				//if (depth.IsFullEmpty())
				//	throw new ArgumentException("Переданный стакан является пустым.", "depths");

				if (depth.BestBid != null && depth.BestAsk != null && depth.BestBid.Price >= depth.BestAsk.Price)
					throw new ArgumentException("Лучший бид {0} больше или равен лучшему офферу {1} во время {2}.".Put(depth.BestBid.Price, depth.BestAsk.Price, depth.LastChangeTime), "depths");

				metaInfo.LastTime = bits.SerializeTime(depth.LastChangeTime, metaInfo.LastTime, "стаканы");

				//metaInfo.PrevTime = dt;	// pyh в предыд строке это уже делается

				var isFull = prevDepth == null;

				bits.Add(isFull); // пишем, полный ли стакан или это дельта

				var delta = isFull ? depth : prevDepth.GetDelta(depth);

				prevDepth = depth;

				SerializeQuotes(bits, delta.Bids, metaInfo, isFull);
				SerializeQuotes(bits, delta.Asks, metaInfo, isFull);

				metaInfo.LastPrice = GetDepthPrice(depth);

				if (metaInfo.Version >= MarketDataVersions.Version40)
					bits.SerializeLong(depth.Latency.Ticks);
			}
		}

		public override MarketDepth MoveNext(MarketDataEnumerator<MarketDepth, MarketDepthMetaInfo> enumerator)
		{
			var metaInfo = enumerator.MetaInfo;
			var reader = enumerator.Reader;

			metaInfo.FirstTime = reader.ReadTime(enumerator.Date, metaInfo.FirstTime);

			var isFull = reader.Read();
			var prevDepth = enumerator.Previous;

			var fromBids = isFull ? new Quote[0] : prevDepth.Bids;
			var fromAsks = isFull ? new Quote[0] : prevDepth.Asks;

			Quote[] bidsDiff;
			Quote[] asksDiff;

			var bids = DeserializeQuotesAndMerge(reader, metaInfo.FirstPrice, metaInfo.MinStepSize, OrderDirections.Buy, fromBids, out bidsDiff);
			var asks = DeserializeQuotesAndMerge(reader, metaInfo.FirstPrice, metaInfo.MinStepSize, OrderDirections.Sell, fromAsks, out asksDiff);

			var depth = new MarketDepth(Security);
			depth.Update(bids, asks, metaInfo.FirstTime);

			var diff = new MarketDepth(Security);
			diff.Update(bidsDiff, asksDiff, metaInfo.FirstTime);
			enumerator.Delta = diff;

			//if (depth.BestBid != null && depth.BestAsk != null && depth.BestBid.Price >= depth.BestAsk.Price)
			//	throw new InvalidOperationException("Лучший бид {0} больше или равен лучшему офферу {1}.".Put(depth.BestBid.Price, depth.BestAsk.Price));

			metaInfo.FirstPrice = GetDepthPrice(depth);

			if (metaInfo.Version >= MarketDataVersions.Version40)
				depth.Latency = reader.ReadLong().To<TimeSpan>();

			return depth;
		}

		private void SerializeQuotes(List<bool> bits, Quote[] quotes, MarketDepthMetaInfo metaInfo, bool isFull)
		{
			if (bits == null)
				throw new ArgumentNullException("bits");

			if (quotes == null)
				throw new ArgumentNullException("quotes");

			if (metaInfo == null)
				throw new ArgumentNullException("metaInfo");

			var prevPrice = metaInfo.LastPrice;

			bits.SerializeInt(quotes.Length);

			foreach (var quote in quotes)
			{
				if (quote.Price <= 0)
					throw new ArgumentOutOfRangeException("quotes", quote.Price, "Неправильная цена котировки.");

				if (quote.Volume < 0 || (isFull && quote.Volume == 0))
					throw new ArgumentOutOfRangeException("quotes", quote.Volume, "Неправильный объем котировки.");

				bits.SerializePrice(quote.Price, prevPrice, metaInfo.MinStepSize, Security);
				bits.SerializeVolume(quote.Volume);

				prevPrice = quote.Price;
			}
		}

		private Quote[] DeserializeQuotesAndMerge(OptimizedBitArrayReader reader, decimal prevPrice, decimal minStepSize, OrderDirections orderDirection, Quote[] from, out Quote[] diff)
		{
			var deltaCount = reader.ReadInt();
			diff = new Quote[deltaCount];

			if (deltaCount == 0)
				return from;

			var security = Security;

			var result = new Quote[deltaCount + from.Length];

			var outIndex = 0;
			var fromIndex = 0;
			var deltaIndex = 0;

			var iprice = (int)(prevPrice / minStepSize);

			//iprice += reader.DeserializeInt();
			//var ivolume = reader.InlinedDeserializeVolume();
			long ivolume;
			int dprice;

			reader.ReadPriceVolume(out dprice, out ivolume);
			iprice += dprice;

			if (from.Length > 0)
			{
				var k = orderDirection == OrderDirections.Buy ? 1 : -1;

				var fromQuote = from[0];
				var ifromPrice = fromQuote.PriceIndex;

				while (true)
				{
					var cmp = k * (ifromPrice - iprice);
					if (cmp > 0)
					{
						result[outIndex++] = fromQuote;
						fromIndex++;

						if (fromIndex < from.Length)
						{
							fromQuote = from[fromIndex];
							ifromPrice = fromQuote.PriceIndex;
						}
						else
							break;
					}
					else if (cmp < 0)
					{
						// новая квота
						var quote = new Quote(security, iprice * minStepSize, ivolume, orderDirection) { PriceIndex = iprice };

						result[outIndex++] = quote;
						diff[deltaIndex] = new Quote(security, quote.Price, ivolume, orderDirection){ PriceIndex = iprice };

						deltaIndex++;

						if (deltaIndex < deltaCount)
						{
							//iprice += reader.DeserializeInt();
							//ivolume = reader.InlinedDeserializeVolume();
							reader.ReadPriceVolume(out dprice, out ivolume);
							iprice += dprice;
						}
						else
							break;
					}
					else
					{
						// измененная квота
						diff[deltaIndex] = new Quote(security, fromQuote.Price, ivolume-fromQuote.Volume, orderDirection) {PriceIndex = iprice};

						if (ivolume > 0)
						{
							var quote = new Quote(security, fromQuote.Price, ivolume, orderDirection) { PriceIndex = iprice };
							result[outIndex++] = quote;
						}

						deltaIndex++;
						fromIndex++;

						if (deltaIndex < deltaCount)
						{
							//iprice += reader.DeserializeInt();
							//ivolume = reader.InlinedDeserializeVolume();
							reader.ReadPriceVolume(out dprice, out ivolume);
							iprice += dprice;
						}
						else
							break;

						if (fromIndex < from.Length)
						{
							fromQuote = from[fromIndex];
							ifromPrice = fromQuote.PriceIndex;
						}
						else
							break;
					}
				}
			}


			if (deltaIndex < deltaCount)
			{
				while (true)
				{
					var quote = new Quote(security, iprice * minStepSize, ivolume, orderDirection) { PriceIndex = iprice };
					result[outIndex++] = quote;
					diff[deltaIndex] = quote;
					deltaIndex++;
					if (deltaIndex < deltaCount)
					{
						//iprice += reader.DeserializeInt();
						//ivolume = reader.InlinedDeserializeVolume();		
						reader.ReadPriceVolume(out dprice, out ivolume);
						iprice += dprice;
					}
					else
						break;
				}
			}

			Array.Copy(from, fromIndex, result, outIndex, from.Length - fromIndex);
			outIndex += from.Length - fromIndex;

			Array.Resize(ref result, outIndex);

			return result;
		}

		private static decimal GetDepthPrice(MarketDepth depth)
		{
			//if (depth == null)
			//	throw new ArgumentNullException("depth");

			var bestBid = depth.BestBid;
			if (!ReferenceEquals(bestBid, null))
				return bestBid.Price;

			var bestAsk = depth.BestAsk;
			if (!ReferenceEquals(bestAsk, null))
				return bestAsk.Price;

			return 0m;
		}
	}
}